/**
 * @file SPTest.cpp
 *
 * @author Luc Fourestier
 * @date 2009
 */

#include "SPTest.h"

#include <stdio.h>
#include <sys/stat.h>
#include <pthread.h>

#include "cute.h"
#include "StreamPlayer.h"
#include "StreamAnalyser.h"
#include "TS.h"

#define MAX_BUFF_SIZE   1500
#define MAX_CB_ELT 1000
#define PACKET_PER_BUFFER 7 //  7 max

static void * UDPThread(void * Arg);

/**
 * Test StreamPlayer Creation/Deletion for UDP output
 *
 */
void SPTest_CreateDeleteUdp()
{
#define WAIT_TIME 100000
#define PORT	9400
#define ADDR	"127.0.0.1"

    SP_Option_t Options;
    SP_StreamPlayer_t * TestSP_p = NULL;
	int Error = OK;

	bzero((void*)&Options, sizeof(SP_Option_t));
	Error = CircB_CreateCircBuff(&Options.CB, MAX_BUFF_SIZE+sizeof(Tag_t), MAX_CB_ELT);
	ASSERT_EQUALM("Error", 0, Error);
 	gettimeofday(&Options.ReferenceTime, NULL);
 	Options.OutputType = Udp;
 	strcpy(Options.SendAddress, ADDR);
 	Options.SendPort = PORT;

 	// Test creation
 	Error = SP_Create(&TestSP_p, Options);

	ASSERT_EQUALM("Error", 0, Error);
	ASSERTM("TestSP_p is NULL", TestSP_p != NULL);
	ASSERT_EQUALM("TestSP_p->Options.OutputType", (int)Udp, (int)TestSP_p->Options.OutputType);
	ASSERT_EQUALM("TestSP_p->Options.SendPort", PORT, TestSP_p->Options.SendPort);
	Error = strcmp(ADDR, TestSP_p->Options.SendAddress);
	ASSERT_EQUALM("TestSP_p->Options.SendAddress", 0, Error);
	ASSERT_EQUALM("TestSP_p->Options.FileName", 0, TestSP_p->Options.FileName[0]);
	ASSERTM("TestSP_p->Options.CB is NULL", TestSP_p->Options.CB != NULL);
	ASSERT_EQUALM("TestSP_p->ThreadLoop", 1, TestSP_p->ThreadLoop);
	ASSERTM("TestSP_p->StorageFile is not NULL", TestSP_p->StorageFile == NULL);

	printf("Waiting %i µs...\n", WAIT_TIME);
	usleep(WAIT_TIME);

	ASSERTM("TestSP_p->Socket is 0", TestSP_p->Socket != 0);

    // Delete
	Error = SP_Delete(TestSP_p);
	ASSERT_EQUALM("Error", 0, Error);

	Error = CircB_DeleteCircBuff(Options.CB);
	ASSERT_EQUALM("Error", 0, Error);

#undef WAIT_TIME
#undef PORT
#undef ADDR
}

/**
 * Test StreamPlayer Creation/Deletion for File output
 *
 */
void SPTest_CreateDeleteFile()
{
#define WAIT_TIME 100000
#define FILENAME "Dump.ts"

    SP_Option_t Options;
    SP_StreamPlayer_t * TestSP_p = NULL;
	int Error = OK;

	bzero((void*)&Options, sizeof(SP_Option_t));
	Error = CircB_CreateCircBuff(&Options.CB, MAX_BUFF_SIZE+sizeof(Tag_t), MAX_CB_ELT);
	ASSERT_EQUALM("Error", 0, Error);
 	gettimeofday(&Options.ReferenceTime, NULL);
 	Options.OutputType = File;
 	strcpy(Options.FileName, FILENAME);

 	// Test creation
 	Error = SP_Create(&TestSP_p, Options);

	ASSERT_EQUALM("Error", 0, Error);
	ASSERTM("TestSP_p is NULL", TestSP_p != NULL);
	ASSERT_EQUALM("TestSP_p->Options.OutputType", (int)File, (int)TestSP_p->Options.OutputType);
	ASSERT_EQUALM("TestSP_p->Options.SendPort", 0, TestSP_p->Options.SendPort);
	Error = strcmp(FILENAME, TestSP_p->Options.FileName);
	ASSERT_EQUALM("TestSP_p->Options.FileName", 0, Error);
	ASSERTM("TestSP_p->Options.CB is NULL", TestSP_p->Options.CB != NULL);
	ASSERT_EQUALM("TestSP_p->ThreadLoop", 1, TestSP_p->ThreadLoop);

	printf("Waiting %i µs...\n", WAIT_TIME);
	usleep(WAIT_TIME);

	ASSERTM("TestSP_p->StorageFile is NULL", TestSP_p->StorageFile != NULL);

    // Delete
	Error = SP_Delete(TestSP_p);
	ASSERT_EQUALM("Error", 0, Error);

	Error = CircB_DeleteCircBuff(Options.CB);
	ASSERT_EQUALM("Error", 0, Error);

#undef WAIT_TIME
#undef FILENAME
}

#define WAIT_TIME 2
#define PORT	9400
#define ADDR	"127.0.0.1"
#define BIND_ADDR	"127.0.0.1"
#define INPUT_LENGTH 50

unsigned int TotalRxCount = 0;
unsigned int ThreadLoop = 1;
int Socket = 0;
struct timeval Sendtime;
unsigned int SendError = 0;


/**
 * Test StreamPlayer when playing to UDP
 */
void SPTest_SendUDP()
{
    SP_Option_t Options;
    SP_StreamPlayer_t * TestSP_p = NULL;
	int Error = OK;

	struct stat s;
	char TestFileName[INPUT_LENGTH];
	FILE * TestFile = NULL;
	int ReadCount = 0;

	pthread_t ThreadID;

	unsigned char Buffer[MAX_CHUNK_SIZE+sizeof(Tag_t)];

	for (int i=34; i<=37; i++)
	{
		// Check test files
		snprintf(TestFileName, INPUT_LENGTH, "test%i.ts", i);
		printf("Testing with file: %s\n", TestFileName);
		stat(TestFileName, &s);
		TestFile = fopen(TestFileName, "r");
		ASSERTM("TestFile is NULL", TestFile != NULL);

		// Start reception
		ThreadLoop = 1;
		TotalRxCount = 0;
		Socket = 0;
		SendError = 0;
		Error = pthread_create(&ThreadID, NULL, UDPThread, NULL);
		ASSERT_EQUALM("Error", 0, Error);

		// Create
		bzero((void*)&Options, sizeof(SP_Option_t));
		Error = CircB_CreateCircBuff(&Options.CB, MAX_CHUNK_SIZE+sizeof(Tag_t), MAX_CB_ELT);
		ASSERT_EQUALM("Error", 0, Error);
		gettimeofday(&Options.ReferenceTime, NULL);
		Options.OutputType = Udp;
	 	strcpy(Options.SendAddress, ADDR);
	 	Options.SendPort = PORT;

		Error = SP_Create(&TestSP_p, Options);
		ASSERT_EQUALM("Error", 0, Error);

		// timestamping
		gettimeofday(&Sendtime, NULL);
		Sendtime.tv_sec += 1; // Send the chunk 1 second later

		// Fill the circular buffer
		do
		{
			bzero((void *)Buffer, MAX_CHUNK_SIZE+sizeof(Tag_t));
			ReadCount = fread((void*)(Buffer+sizeof(Tag_t)), 1, MAX_CHUNK_SIZE, TestFile);
			if (ReadCount > 0)
			{
				unsigned int Size = ReadCount + sizeof(Tag_t);
				((Tag_t*)Buffer)->ReadyToPlay = 1;
				timersub(&Sendtime, &Options.ReferenceTime, &(((Tag_t*)Buffer)->TimeToSend)); // Relative time
				CircB_Write(Options.CB, (void *)Buffer, &Size, CIRCB_NO_WAIT);
			}
		}
		while ((ReadCount > 0) && (!feof(TestFile)));

		printf("Waiting %i s...\n", WAIT_TIME);
		sleep(WAIT_TIME);

		ASSERT_EQUALM("SendError", 0, SendError);
		ASSERT_EQUALM("Reception size", (int)s.st_size, TotalRxCount);

		// Delete
		ThreadLoop = 0;
		shutdown(Socket, SHUT_RDWR);
		pthread_join(ThreadID, NULL);

		if (TestFile != NULL)
		{
			fclose(TestFile);
		}

		Error = SP_Delete(TestSP_p);
		ASSERT_EQUALM("Error", 0, Error);

		Error = CircB_DeleteCircBuff(Options.CB);
		ASSERT_EQUALM("Error", 0, Error);
	}
}

/**
 * The UDP socket reception thread
 *
 * @param Arg: the thread argument
 */
static void * UDPThread(void * Arg)
{
	struct sockaddr_in LocalAddr;
	struct sockaddr_in ClientAddr;
    unsigned int opt = 1;
	unsigned char Buffer[MAX_CHUNK_SIZE+sizeof(Tag_t)];
	struct timeval NewTime;

	// Create reception socket
	Socket = socket(AF_INET, SOCK_DGRAM, 0);
    if (Socket < 0)
    {
    	pthread_exit(NULL);
    }

    setsockopt(Socket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    bzero((char *) &LocalAddr, sizeof(LocalAddr));
    LocalAddr.sin_family = AF_INET;
    LocalAddr.sin_addr.s_addr = inet_addr(BIND_ADDR);
    LocalAddr.sin_port = htons(PORT);
    if (bind(Socket, (struct sockaddr *) &LocalAddr, sizeof(LocalAddr)) < 0)
    {
    	pthread_exit(NULL);
    }

    while(ThreadLoop)
	{
		int Len = 0;
	    int RecvCount = 0;

		// Receive chunks
	    bzero((void *)Buffer, MAX_CHUNK_SIZE+sizeof(Tag_t));
		Len = sizeof(struct sockaddr_in);
		RecvCount = recvfrom(Socket, (void *)Buffer, MAX_CHUNK_SIZE, 0, (struct sockaddr*) &ClientAddr, (socklen_t *) &Len);
		if (RecvCount > 0)
		{
			gettimeofday(&NewTime, NULL);
			printf("Received packet at %i-%i, expected at %i-%i\n", (int)NewTime.tv_sec, (int)NewTime.tv_usec, (int)Sendtime.tv_sec, (int)Sendtime.tv_usec);
			if (timercmp(&NewTime, &Sendtime, <))
			{
				SendError++;
			}
			TotalRxCount += RecvCount;
		}
	}

	pthread_exit(NULL);
}
#undef WAIT_TIME
#undef PORT
#undef ADDR
#undef INPUT_LENGTH


/**
 * Test StreamPlayer when playing into a file
 */
void SPTest_DumpFile()
{
#define WAIT_TIME 1000000
#define FILENAME "Dump.ts"
#define INPUT_LENGTH 50

    SP_Option_t Options;
    SP_StreamPlayer_t * TestSP_p = NULL;
	int Error = OK;

	struct stat s;
	struct stat t;
	char TestFileName[INPUT_LENGTH];
	FILE * TestFile = NULL;
	int ReadCount = 0;

	unsigned char Buffer[MAX_CHUNK_SIZE+sizeof(Tag_t)];

	for (int i=34; i<=37; i++)
	{
		// Check test files
		snprintf(TestFileName, INPUT_LENGTH, "test%i.ts", i);
		printf("Testing with file: %s\n", TestFileName);
		stat(TestFileName, &s);
		TestFile = fopen(TestFileName, "r");
		ASSERTM("TestFile is NULL", TestFile != NULL);

		// Create
		bzero((void*)&Options, sizeof(SP_Option_t));
		Error = CircB_CreateCircBuff(&Options.CB, MAX_CHUNK_SIZE+sizeof(Tag_t), MAX_CB_ELT);
		ASSERT_EQUALM("Error", 0, Error);
		gettimeofday(&Options.ReferenceTime, NULL);
		Options.OutputType = File;
		strcpy(Options.FileName, FILENAME);

		Error = SP_Create(&TestSP_p, Options);
		ASSERT_EQUALM("Error", 0, Error);

		// Fill the circular buffer
		do
		{
			bzero((void *)Buffer, MAX_CHUNK_SIZE+sizeof(Tag_t));
			ReadCount = fread((void*)(Buffer+sizeof(Tag_t)), 1, MAX_CHUNK_SIZE, TestFile);
			if (ReadCount > 0)
			{
				unsigned int Size = ReadCount + sizeof(Tag_t);
				((Tag_t*)Buffer)->ReadyToPlay = 1;
				CircB_Write(Options.CB, (void *)Buffer, &Size, CIRCB_NO_WAIT);
			}
		}
		while ((ReadCount > 0) && (!feof(TestFile)));

		printf("Waiting %i µs...\n", WAIT_TIME);
		usleep(WAIT_TIME);

		// Delete
		if (TestFile != NULL)
		{
			fclose(TestFile);
		}

		Error = SP_Delete(TestSP_p);
		ASSERT_EQUALM("Error", 0, Error);

		Error = CircB_DeleteCircBuff(Options.CB);
		ASSERT_EQUALM("Error", 0, Error);

		// Check the dump (Needs to be after the closure of all files
		stat(FILENAME, &t);

		ASSERT_EQUALM("Dump file size", (int)s.st_size, (int)t.st_size);

	}

#undef WAIT_TIME
#undef FILENAME
#undef INPUT_LENGTH
}


